package org.dslforge.aurum;

import static org.dslforge.aurum.CharSet.any;

import java.util.Arrays;
import java.util.Collection;

import org.dslforge.aurum.Automata.Transition;
import org.dslforge.aurum.SemanticAction.Type;

public class Pattern implements Cloneable {
	private static SemanticAction DUMMY = new SemanticAction(Type.RecognizeTerminal, "$DUMMY",
			Arrays.asList("$DUMMY"));

	public static Pattern or(Pattern... choices) {
		Pattern result = new Pattern();
		result.automata = new Automata<CharSet, SemanticAction>(2);
		for (Pattern choice : choices) {
			int start = result.automata.mergeTransitions(choice.automata);
			result.automata.connect(0, CharSet.EPSILON, start);
			result.automata.connect(choice.tail + start, CharSet.EPSILON, 1);
		}
		return result;
	}

	public static Pattern upTo(Pattern pattern) {
		return new Pattern(any()).kleeneClosure().concatenate(pattern).concatenate(
				new Pattern(any()).kleeneClosure()).negative().concatenate(pattern);
	}

	Automata<CharSet, SemanticAction> automata;

	int tail = 1;

	public Pattern(CharSet charSet) {
		automata = new Automata<CharSet, SemanticAction>(2);
		automata.connect(0, charSet, 1);
	}

	public Pattern(String literal) {
		automata = new Automata<CharSet, SemanticAction>(literal.length() + 1);
		tail = 0;
		for (int i = 0; i < literal.length(); i++)
			automata.connect(tail, new CharSet().add(literal.charAt(i)), ++tail);
	}

	private Pattern() {
	}

	public Pattern concatenate(Pattern other) {
		int start = automata.mergeTransitions(other.automata);
		automata.connect(tail, CharSet.EPSILON, start);
		tail = other.tail + start;
		return this;
	}

	public Pattern kleeneClosure() {
		return iterate().optional();
	}

	public Pattern iterate() {
		automata.connect(tail, CharSet.EPSILON, 0);
		return this;
	}

	public Pattern optional() {
		automata.connect(0, CharSet.EPSILON, tail);
		return this;
	}

	public Pattern repeat(int times) {
		return repeat(times, times);
	}

	public Pattern repeat(int least, int most) {
		Pattern[] patterns = new Pattern[most - 1];
		for (int i = 0; i < most - 1; i++)
			patterns[i] = i < least - 1 ? (Pattern) clone() : ((Pattern) clone()).optional();
		for (Pattern pattern : patterns)
			concatenate(pattern);
		return this;
	}

	public Pattern negative() {
		automata.setAccept(tail, DUMMY);
		Automata<CharSet, Collection<SemanticAction>> deterministic = new SubsetDeterminizer(
				automata).determinize();
		Pattern negative = new Pattern();
		negative.automata = new Automata<CharSet, SemanticAction>(0);
		negative.automata.mergeTransitions(deterministic);
		int sink = negative.automata.newState();
		negative.tail = negative.automata.newState();
		negative.automata.connect(sink, any(), sink);
		for (int state = 0; state < sink; state++) {
			CharSet joint = any().remove(alphabetCharSet(negative.automata, state));
			if (!joint.isEmpty())
				negative.automata.connect(state, joint, sink);
		}
		for (int i = 0; i < negative.tail; i++)
			if (!deterministic.acceptStates.containsKey(i))
				negative.automata.connect(i, CharSet.EPSILON, negative.tail);
		return negative;
	}

	private CharSet alphabetCharSet(Automata<CharSet, ?> automata, int state) {
		CharSet charSet = new CharSet();
		for (Transition<CharSet> transition : automata.transitionTable.get(state))
			charSet.add(transition.symbols);
		return charSet;
	}

	@Override
	public Object clone() {
		Pattern clone = new Pattern();
		clone.automata = new Automata<CharSet, SemanticAction>(automata.stateCount());
		for (int i = 0; i < automata.transitionTable.size(); i++)
			for (Transition<CharSet> transition : automata.transitionTable.get(i))
				clone.automata.connect(i, (CharSet) transition.symbols.clone(),
						transition.destination);
		clone.tail = tail;
		return clone;
	}
}